home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Speccy ClassiX 1998
/
Speccy ClassiX 98.iso
/
amiga_system
/
the_aminet
/
dev
/
gcc
/
ixemulsrc.lha
/
ixemul-41.4
/
library
/
vfork.c
< prev
next >
Wrap
C/C++ Source or Header
|
1995-09-27
|
20KB
|
643 lines
/*
* This file is part of ixemul.library for the Amiga.
* Copyright (C) 1991, 1992 Markus M. Wild
* Portions Copyright (C) 1994 Rafael W. Luebbert
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the Free
* Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*
* $Id: vfork.c,v 1.9 1994/06/19 15:18:29 rluebbert Exp $
*
* $Log: vfork.c,v $
* Revision 1.9 1994/06/19 15:18:29 rluebbert
* *** empty log message ***
*
* Revision 1.7 1992/10/20 16:29:49 mwild
* allow a vfork'd process to use the parents memory pool. The new function
* vfork2() continues to use the old semantics.
*
* Revision 1.6 1992/09/14 01:48:11 mwild
* move kmalloc() out of Forbid() (since the allocator is now Semaphore-based).
* move errno assignment after sigsetmask (thanks Niklas!)
* remove dead code
*
* Revision 1.5 1992/08/09 21:01:43 amiga
* change to 2.x header files
* duplicate calling stack frame in vfork_resume() instead of just doing rts.
* temporary abort calling 1.3 vfork, until that's fixed again (when???).
*
* Revision 1.4 1992/07/04 19:24:12 mwild
* get passing of environment right.
* change ix_sleep() calls to new semantics.
*
* Revision 1.3 1992/05/18 12:26:25 mwild
* fixed bad typo that didn't close files before sending wait message.
* Set childs Input()/Output() to NIL:, we only keep the files in our
* own filetable.
*
* Revision 1.2 1992/05/18 01:02:31 mwild
* add temporary Delay(100) before CloseLibrary() in the child after
* vfork(), there seem to arrive some late packets (don't know why..)
* pass NIL: filehandles as Input()/Output() to the child, so that the
* real I/O-handles only depend on ix-filetable for usage-count
*
* Revision 1.1 1992/05/14 19:55:40 mwild
* Initial revision
*
*/
#define KERNEL
#include "ixemul.h"
#include "kprintf.h"
#include <sys/syscall.h>
#include <sys/resource.h>
#include <sys/wait.h>
#include <stddef.h>
#include <setjmp.h>
void vfork_own_malloc ();
void volatile vfork_longjmp (jmp_buf, int);
extern char **dupvec (char **);
#include <utility/tagitem.h>
#include <dos/dostags.h>
/* having it in a struct makes parameter passing easier */
struct reg_parms {
jmp_buf jb;
};
struct vfork_msg {
struct Message vm_msg;
struct Process *vm_self; /* for validation purposes (1.3, grrr) */
struct Process *vm_pptr;
struct reg_parms *vm_regs; /* parents context to restore */
int vm_own_malloc;
int vm_rc; /* 0 if the child started normally, else errno */
};
struct death_msg {
struct MinNode dm_node;
struct Process *dm_child;
int dm_pgrp;
int dm_status;
struct rusage dm_rusage;
};
/* this is the new process generated by vfork () ! */
static void
launcher ()
{
void *ixb = OpenLibrary ("ixemul.library", IX_VERSION);
struct Process *me = (struct Process *) FindTask (0);
struct vfork_msg *vm;
int omask;
/* the launcher() generated by Arp's ASyncRun seems to get a weird
message. Verify that the message at least looks right.. (damn 1.3 support..) */
vm = 0;
do
{
if (! vm)
WaitPort (& me->pr_MsgPort);
vm = (struct vfork_msg *) GetMsg (& me->pr_MsgPort);
}
while (vm->vm_self != me);
if (ixb)
{
/* get parents user area */
volatile struct user *pu = (struct user *) (vm->vm_pptr->pr_Task.tc_TrapData);
/* `my' user area. This way we don't have to recalculate it too often */
volatile struct user *mu = &u;
/* reaping the dup function of execve() ;-)) */
int fd, rc;
/* link ourselves into the parents process lists. Guarantee single
* threaded access to those lists by locking out any other users of
* the library (nicer than to just call Forbid()) */
ix_lock_base ();
/* our older sybling is the last recently created child of the parent */
mu->p_osptr = pu->p_cptr;
/* we have no younger sybling */
mu->p_ysptr = 0;
/* if we have an older sybling, point its `younger sybling' field at us */
if (mu->p_osptr)
{
struct user *ou = (struct user *) (mu->p_osptr->pr_Task.tc_TrapData);
ou->p_ysptr = me;
}
/* set the parents `last recently created child' field at us */
pu->p_cptr = me;
/* inherit the process group of our parent */
mu->p_pgrp = pu->p_pgrp;
mu->p_pptr = vm->vm_pptr;
/* if we got our own malloc list already, it is save to call malloc here.
If not, the stuff done here is postponed to either vfork_resume, or
execve */
if (vm->vm_own_malloc)
{
/* inherit these global variables. */
mu->u_environ = (char ***) malloc (4);
* mu->u_environ = dupvec (* pu->u_environ);
mu->u_errno = (int *) malloc (4);
*mu->u_errno = 0;
}
else
{
/* borrow the variables of the parent */
mu->u_environ = pu->u_environ;
mu->u_errno = pu->u_errno;
/* tell malloc to use the parents malloc lists */
mu->u_mdp = pu->u_mdp;
}
/* and inherit several other things as well, upto not including u_md */
bcopy (& pu->u_signal[0], & mu->u_signal[0],
offsetof (struct user, u_md) - offsetof (struct user, u_signal[0]));
/* some things have been copied that should be reset */
mu->p_flag &= ~SFREEA4;
bzero (& mu->u_ru, sizeof (struct rusage));
bzero (& mu->u_cru, sizeof (struct rusage));
bzero (& mu->u_timer[0], sizeof (struct itimerval)); /* just the REAL timer! */
syscall (SYS_gettimeofday, & mu->u_start, 0);
omask = vm->vm_rc; /* signal mask to restore at the end */
/* and adjust the open count of each of the copied filedescriptors */
for (fd = 0; fd < NOFILE; fd++)
if (mu->u_ofile[fd])
mu->u_ofile[fd]->f_count++;
/* copying finished, allow other processes to vfork() as well ;-)) */
ix_unlock_base ();
/* remember the message we have to reply when either _exit() or
* execve() is called */
mu->p_vfork_msg = vm;
vm->vm_rc = 0;
mu->u_save_sp = (void *) get_sp ();
/* we get here when the user does an _exit()
* (so as well after execve() terminates !) */
if (rc = _setjmp (mu->u_jmp_buf))
{
struct death_msg *dm = 0;
int i, errno_dummy;
/* overkill? */
vfork_own_malloc ();
/* reset `mu' in here, setjmp() might have clobbered it */
mu = &u;
/* although this is done in CloseLibrary(), files should
really be closed *before* a death-message is sent to
the parent. */
for (i = 0; i < NOFILE; i++)
if (u.u_ofile[i]) syscall (SYS_close, i);
/* free memory (look that most, best all memory is freed here, as
long as we're not inside Forbid. If memory is freed in CloseLibrary,
it may potentially have to wait for the memory semaphore in buddy-alloc.c,
thus breaking the Forbid! */
all_free ();
/* Since errno (= *u.u_errno) is still used by ix_sleep we point it
to our own temporary errno dummy variable */
u.u_errno = &errno_dummy;
KPRINTF (("&errno = %lx, errno = %ld\n", &errno, errno));
/* KPRINTF (("vforked: _exit in progress, rc = %ld.\n", rc)); */
/* this whole thing only happens if our parent is still alive ! */
if (mu->p_pptr && mu->p_pptr != (struct Process *) 1)
{
rc --;
/* KPRINTF (("vforked: parent alive, zombie-sig = %ld, vfork_msg = $%lx.\n",
pu->p_zombie_sig, mu->p_vfork_msg));*/
pu = (struct user *) (mu->p_pptr->pr_Task.tc_TrapData);
/* send the parent a death message with our return code */
dm = (struct death_msg *) kmalloc (sizeof (struct death_msg));
Forbid ();
if (dm)
{
dm->dm_status = (rc >= 128) ? W_EXITCODE (0, rc & 0x7f)
: W_EXITCODE (rc, 0);
dm->dm_rusage = mu->u_ru;
ruadd (&dm->dm_rusage, &mu->u_cru);
dm->dm_child = (struct Process *) FindTask (0);
dm->dm_pgrp = mu->p_pgrp;
KPRINTF (("vfork-exit: Adding child $%lx to $%lx\n", dm->dm_child, mu->p_pptr));
AddTail ((struct List *) &pu->p_zombies, (struct Node *) dm);
}
_psignal (mu->p_pptr, SIGCHLD);
/* have to wakeup the parent `by hand' to make sure it gets
out of its sleep, since it might have SIGCHLD masked out or
ignored at the moment */
if (pu->p_stat == SSLEEP && pu->p_wchan == (caddr_t) pu)
ix_wakeup (pu);
if (mu->p_vfork_msg)
ReplyMsg ((struct Message *) mu->p_vfork_msg);
/* KPRINTF (("vforked: unlinking from parent process chains\n"));*/
/* unlink us from the parents process chains */
if (mu->p_ysptr)
{
struct user *yu = (struct user *) (mu->p_ysptr->pr_Task.tc_TrapData);
yu->p_osptr = mu->p_osptr;
}
if (mu->p_osptr)
{
struct user *ou = (struct user *) (mu->p_osptr->pr_Task.tc_TrapData);
ou->p_ysptr = mu->p_ysptr;
}
if (pu->p_cptr == me)
pu->p_cptr = mu->p_osptr;
/* moved this code into the if-block, as it should only be
executed if there really is a parent that might wait for the
child to terminate (ignore that `this can't happen', perhaps
it does... */
/* this seems to be necessary for process synchronisation, or
else it is possible that the same process address is
reused before the parent noticed the death of this child,
and this would rather confuse it (or ksh at least ;-)) */
if (dm)
ix_sleep (dm, "vfork-dm");
}
else
{
Forbid ();
KPRINTF (("vforked: couldn't send death_msg\n"));
}
KPRINTF (("vforked: now closing library\n"));
/* temporary `fix'.. there seem to be some packets arriving
too late.. */
Delay (100);
CloseLibrary (ixb);
KPRINTF (("vforked: falling off the edge of the world.\n"));
/* just fall off the edge of the world, this is a process */
return;
}
syscall (SYS_sigsetmask, omask);
KPRINTF (("vforked: jumping back\n"));
/* jump into nevereverland ;-) */
vfork_longjmp (vm->vm_regs->jb, 0);
/* NOTREACHED */
}
vm->vm_rc = ENOMEM; /* can't imagine any other reason why the OpenLib should fail */
ReplyMsg ((struct Message *) vm);
/* fall off the edge of the world ;-) */
}
/* This function is used by vfork_resume and execve. Perhaps it should be made
externally available? It causes the process to switch to its own malloc
list, and copies errno and environ into private space. */
void
vfork_own_malloc (void)
{
/* use volatile here, or the compiler might do wrong `optimization' .. */
volatile struct user *p = &u;
if (p->u_mdp != &p->u_md)
{
char **parent_environ = * p->u_environ;
/* switch to our memory list (which is initialized by OpenLibrary) */
p->u_mdp = &p->u_md;
/* dupvec now uses malloc() on our list */
p->u_environ = (char ***) malloc (4);
*p->u_environ = dupvec (parent_environ);
}
}
/*
* this is an implementation extension to the `real' vfork2(). Normally you
* can only cause the parent to resume by calling _exit() or execve() from
* the child. Since I can't provide a real fork() on the Amiga, this function
* is a third possibility to make the parent resume. You have then two
* concurrent processes sharing the same frame and global data... Please be
* EXTREMELY careful what you may do and what not. vfork2() itself is a hack,
* this is an even greater one...
*
* DO NOT use this function in combination with vfork(), or you'll get a big
* memory leak. Only use it with vfork2().
*
* Note that this function should really be static, but if you make it static
* and compile it with "gcc -O4", it appears to go away completely. This may
* be a bug in gcc 2.6.3 or it may not, but making it global works around
* the problem for now. This should be tracked down and either the gcc
* bug fixed, or the makefile changed to treat vfork specially. FIXME-fnf
*
*/
/* static */ void
_vfork_resume (u_int *copy_from_sp)
{
struct vfork_msg **vm = &u.p_vfork_msg;
if (*vm)
{
u_int *sp = (u_int *)u.u_save_sp;
u_int *copy_till_sp = (u_int *)get_sp ();
/* be sure to switch to our memory list */
vfork_own_malloc ();
/* copy the stack frame */
copy_from_sp++;
do
*--sp = *--copy_from_sp;
while (copy_from_sp > copy_till_sp);
set_sp ((u_int) sp);
ReplyMsg ((struct Message *) *vm);
*vm = 0;
}
}
asm ("
.globl _vfork
.globl _vfork2
.globl _vfork_resume
_vfork:
| store a setjmp () compatible frame on the stack to pass to _vfork ()
lea sp@(-18*4),sp | _JBLEN (17) longs on the stack
pea sp@
jbsr _setjmp
lea sp@(4),sp
| now patch sp and pc, since they differ
addl #20*4,sp@(8) | account for buffer space
movel sp@(18*4),sp@(20) | insert real PC (return addr on stack)
| tell _vfork *not* yet to switch to own malloc-list
pea 0
bsr __vfork
lea sp@(18*4 + 4),sp
rts
_vfork2:
| this is the vfork used in older versions of the library
lea sp@(-18*4),sp | _JBLEN (17) longs on the stack
pea sp@
jbsr _setjmp
lea sp@(4),sp
| now patch sp and pc, since they differ
addl #20*4,sp@(8) | account for buffer space
movel sp@(18*4),sp@(20) | insert real PC (return addr on stack)
| tell _vfork to have the child run with own malloc-list.
pea 1
bsr __vfork
lea sp@(18*4 + 4),sp
rts
| the following is longjmp(), with the subtle difference that this
| thing doesn't insist in returning something non-zero...
_vfork_longjmp:
movel sp@(4),a0 /* save area pointer */
tstl a0@(8) /* ensure non-zero SP */
jeq Lbotch /* oops! */
movel sp@(8),d0 /* grab return value */
moveml a0@(28),d2-d7/a2-a4/a6 /* restore non-scratch regs */
movel a0,sp@- /* let sigreturn */
jbsr _sigreturn /* finish for us */
Lbotch:
jsr _longjmperror
stop #0
_vfork_resume:
pea sp@ | pass the sp containing the return address
bsr __vfork_resume
lea sp@(4),sp
rts
");
static int
_vfork (int own_malloc, struct reg_parms rp)
{
struct Process *me = (struct Process *) FindTask(0);
struct CommandLineInterface *CLI = BTOCPTR (me->pr_CLI);
u_int stack_size = CLI ? CLI->cli_DefaultStack * 4 : me->pr_StackSize;
BPTR input, output;
/* those *have* to be in registers to survive the stack deallocation */
register struct vfork_msg *vm asm ("a2");
register int omask asm ("d2");
register struct Process *child asm ("a3");
struct TagItem tags [] = {
{ NP_Entry, (ULONG) launcher, },
{ NP_Cli, (ULONG) (CLI ? -1 : 0), }, /* same thing we are */
{ NP_Name, (ULONG) "vfork()'d process", }, /* to be overridden by execve() */
{ NP_StackSize, stack_size, }, /* same size we use */
{ TAG_END, 0, }
};
KPRINTF (("vfork: creating child with stacksize = $%lx[$%lx,$%lx]\n", stack_size, tags[3]));
vm = (struct vfork_msg *) kmalloc (sizeof (struct vfork_msg));
if (! vm)
{
errno = ENOMEM;
KPRINTF (("&errno = %lx, errno = %ld\n", &errno, errno));
return -1;
}
vm->vm_msg.mn_ReplyPort = u.u_sync_mp;
vm->vm_msg.mn_Node.ln_Type = NT_MESSAGE;
vm->vm_msg.mn_Length = sizeof (struct vfork_msg);
vm->vm_pptr = me;
vm->vm_own_malloc = own_malloc;
vm->vm_regs = & rp;
/* we have to block all signals as long as the child uses our resources.
* but since the child needs to start with the signal mask BEFORE this
* general blocking, we have to pass it the old signal mask. This is a
* way to do it */
vm->vm_rc =
omask = syscall (SYS_sigsetmask, ~0);
/* save the passed frame in our user structure, since the child will
deallocate it from the stack when it `returns' to user code */
bcopy (&rp, &u.u_vfork_frame, sizeof (rp));
KPRINTF (("vfork: creating child with stacksize = $%lx[$%lx,$%lx]\n", stack_size, tags[3]));
child = CreateNewProc (tags);
if (! child)
{
/* do I have to close input/output here? Or does the startup close
them no matter whether it succeeds or not ? */
kfree (vm);
syscall (SYS_sigsetmask, omask);
errno = EPROCLIM;
KPRINTF (("&errno = %lx, errno = %ld\n", &errno, errno));
return -1;
}
/* As soon as this message is dispatched, the child will `return' and
deallocate the stack we're running on. So afterwards, *only* use
register variables and then longjmp () back.
Since we don't have a stack until after the longjmp(), temporarily
switch to our mini-stack */
set_sp ((u_int) &u.u_mini_stack[sizeof (u.u_mini_stack) / sizeof (long)]);
vm->vm_self = child;
PutMsg (& child->pr_MsgPort, (struct Message *) vm);
/* wait until the child does execve() or _exit() */
WaitPort (u.u_sync_mp);
GetMsg (u.u_sync_mp);
syscall (SYS_sigsetmask, omask);
if (vm->vm_rc)
{
errno = (int) vm->vm_rc;
KPRINTF (("&errno = %lx, errno = %ld\n", &errno, errno));
child = (struct Process *) -1;
}
/* this is the parent return, so we pass the id of the new child */
kfree (vm);
/* could use longjmp() here, but since we already *have* the local one.. */
vfork_longjmp (u.u_vfork_frame, (int) child);
}
ruadd(ru, ru2)
register struct rusage *ru, *ru2;
{
register long *ip, *ip2;
register int i;
timevaladd(&ru->ru_utime, &ru2->ru_utime);
timevaladd(&ru->ru_stime, &ru2->ru_stime);
if (ru->ru_maxrss < ru2->ru_maxrss)
ru->ru_maxrss = ru2->ru_maxrss;
ip = &ru->ru_first; ip2 = &ru2->ru_first;
for (i = &ru->ru_last - &ru->ru_first; i > 0; i--)
*ip++ += *ip2++;
}
/* This function is in desperate need of redesign !!!! */
int
wait4 (int pid, int *status, int options, struct rusage *rusage)
{
struct Process * me = (struct Process *) FindTask (0);
struct user * mu = (struct user *) me->pr_Task.tc_TrapData;
int omask;
for (;;)
{
int err = 0, omask;
struct death_msg *dm, *ndm;
int got_node;
got_node = 0;
Forbid ();
for (dm = (struct death_msg *) mu->p_zombies.mlh_Head;
ndm = (struct death_msg *) dm->dm_node.mln_Succ;
dm = ndm)
if (pid == -1 ||
(pid == 0 && dm->dm_pgrp == mu->p_pgrp) ||
(pid < -1 && dm->dm_pgrp == - pid) ||
(pid == (int) dm->dm_child))
{
got_node = 1;
Remove ((struct Node *) dm);
break;
}
if (!got_node && !mu->p_cptr)
err = ECHILD;
if (got_node)
{
struct Process *child;
KPRINTF (("wait4: unlinking child $%lx\n", dm->dm_child));
ix_wakeup (dm);
Permit ();
if (status)
*status = dm->dm_status;
if (rusage)
*rusage = dm->dm_rusage;
child = dm->dm_child;
kfree (dm);
return (int) child;
}
if (!got_node && err)
{
Permit ();
errno = err;
KPRINTF (("&errno = %lx, errno = %ld\n", &errno, errno));
return -1;
}
if (options & WNOHANG)
{
Permit ();
return 0;
}
KPRINTF (("wait4: waiting for SIGCHLD\n"));
ix_sleep (mu, "wait4");
if (mu->p_sig)
err = EINTR;
Permit ();
if (CURSIG (mu))
setrun (me);
}
}